package net.j4love.mybatis.kit.executor;

import net.j4love.mybatis.kit.plugin.XMLEnvConfiguration;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.executor.BatchExecutor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ReuseExecutor;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

public class ExecutorManager {

    private static final Random RANDOM = new Random();
    private static final Map<String , Executor> SLAVE_EXECUTOR_MAP = new ConcurrentHashMap<>();
    private static List<ExecutorEnvPair> SLAVE_EXECUTORENVPAIRS;
    private ExecutorEnvPair masterExecutorEnvPair;

    private XMLEnvConfiguration xmlEnvConfiguration;

    private ExecutorManager(String mybatisConfigPath , List<Class<? extends Interceptor>> excludeInterceptors) {
        try {
            this.xmlEnvConfiguration = XMLEnvConfiguration.getXmlEnvConfigParser(mybatisConfigPath);
            Environment masterEnv = this.xmlEnvConfiguration.getMasterConfiguration().getEnvironment();
            DatabaseEnv masterDatabaseEnv = DatabaseEnv.builder()
                    .envId(masterEnv.getId())
                    .url(masterEnv.getDataSource().getConnection().getMetaData().getURL())
                    .build();
            this.masterExecutorEnvPair = new ExecutorEnvPair(masterDatabaseEnv , null);
            List<ExecutorEnvPair> executorEnvPairs = new ArrayList<>(this.xmlEnvConfiguration.getSlaveEnvSize());
            for (Configuration slaveConfiguration : this.xmlEnvConfiguration.getSlaveConfigurations()) {
                Executor executor = newExecutorExcludeInterceptor(slaveConfiguration , excludeInterceptors);
                Environment env = slaveConfiguration.getEnvironment();
                SLAVE_EXECUTOR_MAP.putIfAbsent(env.getId() , executor);

                DatabaseEnv databaseEnv = DatabaseEnv.builder()
                        .envId(env.getId())
                        .url(env.getDataSource().getConnection().getMetaData().getURL())
                        .build();
                executorEnvPairs.add(new ExecutorEnvPair(databaseEnv, executor));
            }
            SLAVE_EXECUTORENVPAIRS = Collections.synchronizedList(Collections.unmodifiableList(executorEnvPairs));
        } catch (Throwable t) {
            throw new RuntimeException(ExecutorManager.class.getName() + " init error" , t);
        }
    }

    public List<ExecutorEnvPair> getSlaveExecutorEnvPairs() {
        return SLAVE_EXECUTORENVPAIRS;
    }

    public ExecutorEnvPair getRandomSlaveExecutorEnvPair() {
        return SLAVE_EXECUTORENVPAIRS.get(RANDOM.nextInt(SLAVE_EXECUTORENVPAIRS.size()));
    }


    private Executor newExecutorExcludeInterceptor(Configuration configuration , List<Class<? extends Interceptor>> excludeInterceptors) {
        Environment env = configuration.getEnvironment();
        Transaction transaction = env.getTransactionFactory().newTransaction(env.getDataSource(), TransactionIsolationLevel.REPEATABLE_READ, true);
        ExecutorType executorType = configuration.getDefaultExecutorType();
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(configuration, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(configuration, transaction);
        } else {
            executor = new SimpleExecutor(configuration, transaction);
        }

        List<Interceptor> interceptors = configuration.getInterceptors();
        if (CollectionUtils.isNotEmpty(interceptors)) {
            LOOP_OUT:
            for (Interceptor interceptor : interceptors) {
                for (Class<? extends Interceptor> excludeInterceptor : excludeInterceptors) {
                    if (excludeInterceptor.isInstance(interceptor)) {
                        continue LOOP_OUT;
                    }
                }
                executor = (Executor) interceptor.plugin(executor);
            }
        }
        return executor;
    }

    public ExecutorEnvPair getMasterExecutorEnvPair() {
        return masterExecutorEnvPair;
    }

    public static ExecutorManager newExecutorManager(String mybatisConfigPath , List<Class<? extends Interceptor>> excludeInterceptors) {
        return new ExecutorManager(mybatisConfigPath , excludeInterceptors);
    }
}
